package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;

public class Server {
    private ServerSocket serverSocket;
    //共享所有ClientHandler中的输出流便于广播消息使用
  //  private PrintWriter[] allout = {};
    private Collection<PrintWriter> allout = new ArrayList<>();
    public Server(){
        try {
            /**
             * 运行在服务器的ServerSocket主要有两个工作：
             * 1：打开服务端口，客户端就可以通过这个端口与服务端建立连接
             * 2：监听服务端口，一旦一个客户端与服务端建立连接就可以立即
             * 返回一个Socket，通过它与客户端交互
             *
             * 我们可以吧ServerSocket想象为某客服的“总机”，所有客户端
             * 都可以给总机打电话，而总机下面连接着若干"电话"。
             */
            System.out.println("正在启动服务端");
            /**
             * 该端口不能与系统其它应用程序已经打开的端口号一致，否则会
             * 抛出异常：java.net.BindException:address already in use
             * 遇到该错误需要更换端口号，直到不报错为止。
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕！");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public void start(){
        try {
            /**
             * ServerSocket提供的方法：
             * Socket accept()
             * 此方法是一个阻塞方法，调用后服务端程序"卡主"，开始等待
             * 客户端的连接(相当于等待电话)，一旦一个客户端连接了，
             * 此时该方法会理解返回一个Socket(相当于接起一个电话)
             * 通过返回的Socket即可与连接的客户端进行交互
             */
            while (true) {
                System.out.println("等待客户端连接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端连接了！");
                //一个客户端连接了就启动一个线程来处理该客户端交互
                ClientHandler clientHandler = new ClientHandler(socket);
                Thread t= new Thread(clientHandler);
                t.start();



            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server server = new Server();
        server.start();
    }

    /**
     * 该线程任务是负责与指定的客户端交互的
     */
    class ClientHandler implements Runnable{
        private Socket socket;
        private String host;//客户端地址信息

        public ClientHandler(Socket socket){
            this.socket = socket;
            //通过socket获取远端计算机地址的字符串格式
            host = socket.getInetAddress().getHostAddress();

        }
        public void run() {
            PrintWriter pw = null;
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, "UTF-8");
                BufferedReader br = new BufferedReader(isr);
//通过socket获取流连接并通过流连接变成PrintWriter
                pw = new PrintWriter(
                        new BufferedWriter(
                                new OutputStreamWriter(
                                        socket.getOutputStream(),
                                        "UTF-8")
                        ), true
                );
                synchronized (Server.this) {
                    //将输出流存入allout便于其它ClientHandler访问来转发消息
                    //1对allout数组扩容
                    allout.add(pw);
                    //2将当前输出流存入数组最后一个位置

                }
                sendMessage(host + "上线了，当前在线人数:" + allout.size());

                String message;


                /**
                 * 这里的br.readLine()在读取客户端发送过来的一行字符串
                 * 时会形成阻塞现象，如果客户端没有发送内容时，该方法会一直
                 * "卡主"
                 * 如果客户端调用了socket.close()与服务端断开连接，那么
                 * readLine方法会返回null
                 * 如果客户端强行关闭(没有主动调用socket.close()就结束了
                 * 程序)则readLine方法会抛出异常
                 * java.net.SocketException:Connection reset
                 */

                while ((message = br.readLine()) != null) {


                    System.out.println(host + "说" + message);
                    //将消息回复给客户端
                    sendMessage(host + "说:" + message);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                //处理客户端断开后的操作

                synchronized (Server.this) {
                    //将对应当前客户端的输出流从allout中删除
//                    Iterator it = allout.iterator();
//                    for (PrintWriter i : allout) {
//                        if (allout == pw) {
//                            allout.remove(i);
//                            System.out.println(allout);
//                            break;
//                        }
//                    }
//                }
                    allout.remove(pw);
                }

                System.out.println(host + "下线了，当前在线人数:" + allout.size());


                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }

        }

        /**
         * 将消息广播给所有客户端
         * @param message
         */
        private void sendMessage(String message){
            synchronized (Server.this) {
                for (PrintWriter pw :allout) {
                    pw.println(message);
                }
            }
        }
    }
}
